package demo.java.time;

import demo.java.text.DateFormatDemo;
import org.apache.commons.lang3.time.DateUtils;
import org.junit.Test;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.Locale;
import java.util.TimeZone;
import java.util.concurrent.TimeUnit;

public class DateDemo {

    @Test
    public void test() throws ParseException {
        System.out.println(System.currentTimeMillis());
        System.out.println(System.currentTimeMillis() - TimeUnit.MINUTES.toMillis(10));

        System.out.println(System.currentTimeMillis() - 10 * 60 * 1000);
        System.err.println(new Date(1636617660000L));
        System.err.println(new Date(1636617720000L));
        System.err.println(new Date(1636617780000L));

        System.out.println(new Date(-9999999));
        Date date = CalendarDemo.parseDate(0000, 00, 01, 00, 00, 00);
        System.out.println(DateFormatDemo.formatDate(date, DateFormatDemo.G_Y_M_D_HMS));
    }

    @Test
    public void date2Str() throws ParseException {
        Date date = new Date();
        System.out.println(DateTimeDemo.toLocalDateTime(date));
        String dateStr = date.toString();
        System.out.println("date.toString() = " + dateStr);
        SimpleDateFormat sdf = new SimpleDateFormat("EEE MMM dd HH:mm:ss zzz yyyy", Locale.US);
        Date date1 = sdf.parse(dateStr);
        System.out.println(DateTimeDemo.toLocalDateTime(date1));
        System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(date1));
    }

    @Test
    public void printDate2() {
        long[] time = {1633659420000L, 1633659720000L};
        for (long l : time) {
            Date date = new Date(l);
            System.out.println(DateTimeDemo.toLocalDateTime(date));
        }

    }

    @Test
    public void testDateFormat() {
        try {
            Date d2 = DateFormatDemo.parseStr2Date("0000-00-00", DateFormatDemo.Y_M_D);
            System.out.println(DateFormatDemo.formatDate(d2, DateFormatDemo.G_Y_M_D_HMS));
            Date d3 = DateFormatDemo.parseStr2Date("0001-01-01", DateFormatDemo.Y_M_D);
            System.out.println(DateFormatDemo.formatDate(d3, DateFormatDemo.G_Y_M_D_HMS));
            System.out.println(d3.getTime());
            Date d4 = new Date(-62135798400000L);
            System.out.println(DateFormatDemo.formatDate(d4, DateFormatDemo.G_Y_M_D_HMS));
            System.out.println(DateFormatDemo.formatDate(new Date(), "yyyy-MM-dd HH:mm:00"));
        } catch (ParseException e) {
            e.printStackTrace();
        }
    }

    @Test
    public void demoDateUtils() {
        Date now = new Date();
        System.out.println("原始时间：" + DateTimeDemo.toLocalDateTime(now));
        System.out.println("月初： " + DateTimeDemo.toLocalDateTime(DateUtils.truncate(now, Calendar.MONTH)));
        System.out.println(
                "当天开始时间： " + DateTimeDemo.toLocalDateTime(DateUtils.truncate(now, Calendar.DAY_OF_MONTH)));
        System.out.println("当天开始时间： " + DateTimeDemo.toLocalDateTime(DateUtils.truncate(now, Calendar.DATE)));
        System.out.println("当前小时开始时间： " + DateTimeDemo.toLocalDateTime(DateUtils.truncate(now, Calendar.HOUR)));
        System.out.println("当前分钟开始时间： " + DateTimeDemo.toLocalDateTime(DateUtils.truncate(now, Calendar.MINUTE)));
        System.out.println("当前秒开始时间： " + DateTimeDemo.toLocalDateTime(DateUtils.truncate(now, Calendar.SECOND)));
        System.out.println(removeHmsS2(new Date()));
        System.out.println(CalendarDemo.removeHmsS(new Date()));
        Date today = DateUtils.truncate(new Date(), Calendar.DAY_OF_MONTH);
        Date tomorrow = DateUtils.addDays(today, 1);
        System.out.println(today + "    " + tomorrow);

    }

    @Test
    public void demo1() throws ParseException {
        Date date2 = CalendarDemo.parseDate(2000, 6, 12, 13, 13, 13);
        System.out.println(date2);
        System.out.println(new Date(1566440640000L));
    }

    /**
     * 计算两个日期之间相差多少天(endDate-startDate)
     *
     * @param startDate 小的日期
     * @param endDate   大的日期
     * @return Integer
     */
    public static int subtractDays(Date startDate, Date endDate) {
        long increaseDate = (endDate.getTime() - startDate.getTime()) / DateTimeDemo.MILLIS_PER_DAY;
        return (int) increaseDate;
    }

    /**
     * 将带有分隔的时间数据转换成不带分隔符的时间字符串
     *
     * @param srcDatetime
     * @return
     * @如：2014-06-12 11:26:21.143 => 20140612112621143
     * @Description:
     * @version 1.0 2014年7月25日 下午4:28:45 by bsgao create
     */
    public static String replaceDatetime(String srcDatetime) {
        return srcDatetime.replaceAll("\\s|:|-|\\.|\\/", "");
    }

    /**
     * 计算指定日期到现在日期的月数
     *
     * @param dateString
     * @param format
     * @return
     * @throws ParseException
     */
    public static double processMonthsFromNow(String dateString, String format) throws ParseException {
        Date now = new Date();
        Date date = DateFormatDemo.parseStr2Date(dateString, format);
        long howlong = now.getTime() - date.getTime();// 毫秒
        return howlong / (1000 * 60 * 60 * 24 * 30);
    }

    /**
     * 得到指定年到月的天数
     */
    public static int getYearLastDay(int year, int month) {
        int maxDate = 0;
        for (int i = month; i >= 1; i--) {
            maxDate += CalendarDemo.getMonthLastDay(year, i);
        }
        return maxDate;
    }

    /**
     * 去除时分秒
     *
     * @param date
     * @return
     */
    public static Date removeHmsS2(Date date) {
        return DateUtils.truncate(date, Calendar.DAY_OF_MONTH);
    }

    /**
     * TimeUnit
     *
     * @return
     */
    @Test
    public void testTimeUnit() {
        System.out.println("小时转秒：" + TimeUnit.HOURS.toSeconds(1));
        System.out.println("分钟转秒：" + TimeUnit.MINUTES.toSeconds(1));
        System.out.println("秒转秒：" + TimeUnit.MINUTES.toSeconds(1));
    }

    /**
     * Immutable cache mapping a timezone name to its object.
     * We do this because the JDK's TimeZone class was implemented by retards,
     * and it's synchronized, going through a huge pile of code, and allocating
     * new objects all the time.  And to make things even better, if you ask for
     * a TimeZone that doesn't exist, it returns GMT!  It is thus impractical to
     * tell if the timezone name was valid or not.  JDK_brain_damage++;
     * Note: caching everything wastes a few KB on RAM (34KB on my system with
     * 611 timezones -- each instance is 56 bytes with the Sun JDK).
     */
    public static final HashMap<String, TimeZone> timezones;

    static {
        final String[] tzs = TimeZone.getAvailableIDs();
        timezones = new HashMap<>(tzs.length);
        for (final String tz : tzs) {
            timezones.put(tz, TimeZone.getTimeZone(tz));
        }
    }

    /**
     * Attempts to parse a timestamp from a given string
     * Formats accepted are:
     * <ul>
     * <li>Relative: {@code 5m-ago}, {@code 1h-ago}, etc. See
     * {@link #parseDuration}</li>
     * <li>Absolute human readable dates:
     * <ul><li>"yyyy/MM/dd-HH:mm:ss"</li>
     * <li>"yyyy/MM/dd HH:mm:ss"</li>
     * <li>"yyyy/MM/dd-HH:mm"</li>
     * <li>"yyyy/MM/dd HH:mm"</li>
     * <li>"yyyy/MM/dd"</li></ul></li>
     * <li>Unix Timestamp in seconds or milliseconds:
     * <ul><li>1355961600</li>
     * <li>1355961600000</li>
     * <li>1355961600.000</li></ul></li>
     * </ul>
     *
     * @param datetime The string to parse a value for
     * @return A Unix epoch timestamp in milliseconds
     * @throws NullPointerException     if the timestamp is null
     * @throws IllegalArgumentException if the request was malformed
     */
    public static final long parseDateTimeString(final String datetime, final String tz) {
        if (datetime == null || datetime.isEmpty()) {
            return -1;
        }
        if (datetime.matches("^[0-9]+ms$")) {
            return parseLong(datetime.replaceFirst("^([0-9]+)(ms)$", "$1"));
        }

        if (datetime.equalsIgnoreCase("now")) {
            return System.currentTimeMillis();
        }

        if (datetime.toLowerCase().endsWith("-ago")) {
            long interval = parseDuration(
                    datetime.substring(0, datetime.length() - 4));
            return System.currentTimeMillis() - interval;
        }

        if (datetime.toLowerCase().endsWith("-later")) {
            long interval = parseDuration(datetime.substring(0, datetime.length() - 6));
            return System.currentTimeMillis() + interval;
        }

        if (datetime.contains("/") || datetime.contains(":")) {
            try {
                SimpleDateFormat fmt = null;
                switch (datetime.length()) {
                    case 10:
                        fmt = new SimpleDateFormat("yyyy/MM/dd");
                        break;
                    case 16:
                        if (datetime.contains("-"))
                            fmt = new SimpleDateFormat("yyyy/MM/dd-HH:mm");
                        else
                            fmt = new SimpleDateFormat("yyyy/MM/dd HH:mm");
                        break;
                    case 19:
                        if (datetime.contains("-"))
                            fmt = new SimpleDateFormat("yyyy/MM/dd-HH:mm:ss");
                        else
                            fmt = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
                        break;
                    default:
                        // todo - deal with internationalization, other time formats
                        throw new IllegalArgumentException("Invalid absolute date: "
                                + datetime);
                }
                if (tz != null && !tz.isEmpty())
                    setTimeZone(fmt, tz);
                return fmt.parse(datetime).getTime();
            } catch (ParseException e) {
                throw new IllegalArgumentException("Invalid date: " + datetime
                        + ". " + e.getMessage());
            }
        } else {
            try {
                long time;
                final boolean contains_dot = datetime.contains(".");
                // [0-9]{10} ten digits
                // \\. a dot
                // [0-9]{1,3} one to three digits
                final boolean valid_dotted_ms =
                        datetime.matches("^[0-9]{10}\\.[0-9]{1,3}$");
                if (contains_dot) {
                    if (!valid_dotted_ms) {
                        throw new IllegalArgumentException("Invalid time: " + datetime
                                + ". Millisecond timestamps must be in the format "
                                + "<seconds>.<ms> where the milliseconds are limited to 3 digits");
                    }
                    time = parseLong(datetime.replace(".", ""));
                } else {
                    time = parseLong(datetime);
                }
                if (time < 0) {
                    throw new IllegalArgumentException("Invalid time: " + datetime
                            + ". Negative timestamps are not supported.");
                }
                // this is a nasty hack to determine if the incoming request is
                // in seconds or milliseconds. This will work until November 2286
                if (datetime.length() <= 10) {
                    time *= 1000;
                }
                return time;
            } catch (NumberFormatException e) {
                throw new IllegalArgumentException("Invalid time: " + datetime
                        + ". " + e.getMessage());
            }
        }
    }

    /**
     * Parses a human-readable duration (e.g, "10m", "3h", "14d") into seconds.
     * <p>
     * Formats supported:<ul>
     * <li>{@code ms}: milliseconds</li>
     * <li>{@code s}: seconds</li>
     * <li>{@code m}: minutes</li>
     * <li>{@code h}: hours</li>
     * <li>{@code d}: days</li>
     * <li>{@code w}: weeks</li>
     * <li>{@code n}: month (30 days)</li>
     * <li>{@code y}: years (365 days)</li></ul>
     *
     * @param duration The human-readable duration to parse.
     * @return A strictly positive number of milliseconds.
     * @throws IllegalArgumentException if the interval was malformed.
     */
    public static final long parseDuration(final String duration) {
        long interval;
        long multiplier;
        double temp;
        int unit = 0;
        while (Character.isDigit(duration.charAt(unit))) {
            unit++;
            if (unit >= duration.length()) {
                throw new IllegalArgumentException("Invalid duration, must have an "
                        + "integer and unit: " + duration);
            }
        }
        try {
            interval = Long.parseLong(duration.substring(0, unit));
        } catch (NumberFormatException e) {
            throw new IllegalArgumentException("Invalid duration (number): " + duration);
        }
        if (interval <= 0) {
            throw new IllegalArgumentException("Zero or negative duration: " + duration);
        }
        switch (duration.toLowerCase().charAt(duration.length() - 1)) {
            case 's':
                if (duration.charAt(duration.length() - 2) == 'm') {
                    return interval;
                }
                multiplier = 1;
                break;                        // seconds
            case 'm':
                multiplier = 60;
                break;               // minutes
            case 'h':
                multiplier = 3600;
                break;             // hours
            case 'd':
                multiplier = 3600 * 24;
                break;        // days
            case 'w':
                multiplier = 3600 * 24 * 7;
                break;    // weeks
            case 'n':
                multiplier = 3600 * 24 * 30;
                break;   // month (average)
            case 'y':
                multiplier = 3600 * 24 * 365;
                break;  // years (screw leap years)
            default:
                throw new IllegalArgumentException("Invalid duration (suffix): " + duration);
        }
        multiplier *= 1000;
        temp = (double) interval * multiplier;
        if (temp > Long.MAX_VALUE) {
            throw new IllegalArgumentException("Duration must be < Long.MAX_VALUE ms: " + duration);
        }
        return interval * multiplier;
    }

    /**
     * Returns whether or not a date is specified in a relative fashion.
     * <p>
     * A date is specified in a relative fashion if it ends in "-ago",
     * e.g. {@code 1d-ago} is the same as {@code 24h-ago}.
     *
     * @param value The value to parse
     * @return {@code true} if the parameter is passed and is a relative date.
     * Note the method doesn't attempt to validate the relative date.  So this
     * function can return true on something that looks like a relative date,
     * but is actually invalid once we really try to parse it.
     * @throws NullPointerException if the value is null
     */
    public static boolean isRelativeDate(final String value) {
        return value.toLowerCase().endsWith("-ago");
    }

    /**
     * Applies the given timezone to the given date format.
     *
     * @param fmt    Date format to apply the timezone to.
     * @param tzname Name of the timezone, or {@code null} in which case this
     *               function is a no-op.
     * @throws IllegalArgumentException if tzname isn't a valid timezone name.
     * @throws NullPointerException     if the format is null
     */
    public static void setTimeZone(final SimpleDateFormat fmt,
                                   final String tzname) {
        if (tzname == null) {
            return;  // Use the default timezone.
        }
        final TimeZone tz = timezones.get(tzname);
        if (tz != null) {
            fmt.setTimeZone(tz);
        } else {
            throw new IllegalArgumentException("Invalid timezone name: " + tzname);
        }
    }

    /**
     * Sets the default timezone for this running OpenTSDB instance
     * <p>
     * <b>WARNING</b> If OpenTSDB is used with a Security Manager, setting the default
     * timezone only works for the running thread. Otherwise it will work for the
     * entire application.
     * <p>
     *
     * @param tzname Name of the timezone to use
     * @throws IllegalArgumentException if tzname isn't a valid timezone name
     */
    public static void setDefaultTimezone(final String tzname) {
        final TimeZone tz = timezones.get(tzname);
        if (tz != null) {
            TimeZone.setDefault(tz);
        } else {
            throw new IllegalArgumentException("Invalid timezone name: " + tzname);
        }
    }

    public static long currentTimeMillis() {
        return System.currentTimeMillis();
    }

    public static long nanoTime() {
        return System.nanoTime();
    }

    /**
     * Parses an integer value as a long from the given character sequence.
     * <p>
     * This is equivalent to {@link Long#parseLong(String)} except it's up to
     * 100% faster on {@link String} and always works in O(1) space even with
     * {@link StringBuilder} buffers (where it's 2x to 5x faster).
     *
     * @param s The character sequence containing the integer value to parse.
     * @return The value parsed.
     * @throws NumberFormatException if the value is malformed or overflows.
     */
    public static long parseLong(final CharSequence s) {
        final int n = s.length();  // Will NPE if necessary.
        if (n == 0) {
            throw new NumberFormatException("Empty string");
        }
        char c = s.charAt(0);  // Current character.
        int i = 1;  // index in `s'.
        if (c < '0' && (c == '+' || c == '-')) {  // Only 1 test in common case.
            if (n == 1) {
                throw new NumberFormatException("Just a sign, no value: " + s);
            } else if (n > 20) {  // "+9223372036854775807" or "-9223372036854775808"
                throw new NumberFormatException("Value too long: " + s);
            }
            c = s.charAt(1);
            i = 2;  // Skip over the sign.
        } else if (n > 19) {  // "9223372036854775807"
            throw new NumberFormatException("Value too long: " + s);
        }
        long v = 0;  // The result (negated to easily handle MIN_VALUE).
        do {
            if ('0' <= c && c <= '9') {
                v -= c - '0';
            } else {
                throw new NumberFormatException("Invalid character '" + c
                        + "' in " + s);
            }
            if (i == n) {
                break;
            }
            v *= 10;
            c = s.charAt(i++);
        } while (true);
        if (v > 0) {
            throw new NumberFormatException("Overflow in " + s);
        } else if (s.charAt(0) == '-') {
            return v;  // Value is already negative, return unchanged.
        } else if (v == Long.MIN_VALUE) {
            throw new NumberFormatException("Overflow in " + s);
        } else {
            return -v;  // Positive value, need to fix the sign.
        }
    }

}
